﻿using System.Diagnostics;
using System.Text.RegularExpressions;
using System;

namespace WangfsLib.Math
{
    /// <summary>
    /// 一个表示大值数字的类型。支持整数和浮点数。
    /// </summary>
    public sealed class BigNumber
    {
        public static ushort PRECISION = 32;
        public static readonly BigNumber Zero = new();
        public static readonly BigNumber FloatMax = float.MaxValue;
        public static readonly BigNumber FloatMin = float.MinValue;
        public static readonly BigNumber DoubleMax = double.MaxValue;
        public static readonly BigNumber DoubleMin = double.MinValue;
        public static readonly BigNumber LongMax = long.MaxValue;
        public static readonly BigNumber LongMin = long.MinValue;
        public static readonly BigNumber DecimalMax = decimal.MaxValue;
        public static readonly BigNumber DecimalMin = decimal.MinValue;
        /// <summary>
        /// 110 位有效小数位圆周率
        /// </summary>
        public static readonly BigNumber PI_110 = new BigNumber("3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651");
        /// <summary>
        /// 110 位有效小数位圆周率2倍
        /// </summary>
        public static readonly BigNumber PI2_110 = 2 * PI_110;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string _value;
        /// <summary>
        /// 字符串表示的数值
        /// </summary>
        public string Value => this._value;

        /// <summary>
        /// 构造一个表示 0 的大数类型
        /// </summary>
        public BigNumber() => this._value = "0";

        /// <summary>
        /// 用字符串构造一个大数类型
        /// </summary>
        /// <param name="value">数值字符串</param>
        public BigNumber(string value)
        {
            if (!BigNumberPreProcess(value, out this._value)) this._value = "0";
        }

        #region 重载 Object 三大接口

        public override string ToString() => this._value;

        public override bool Equals(object? obj)
        {
            if (obj == null) return false;
            if (obj is string) return this._value.Equals(obj);
            if (obj is BigNumber) return this._value == ((BigNumber)obj)._value;
            return false;
        }

        public override int GetHashCode() => this._value.GetHashCode();

        #endregion

        #region 类型转换 加减乘除 逻辑运算

        public static implicit operator BigNumber(string value) => new(value);
        public static implicit operator BigNumber(sbyte value) => new(value.ToString());
        public static implicit operator BigNumber(byte value) => new(value.ToString());
        public static implicit operator BigNumber(short value) => new(value.ToString());
        public static implicit operator BigNumber(ushort value) => new(value.ToString());
        public static implicit operator BigNumber(int value) => new(value.ToString());
        public static implicit operator BigNumber(uint value) => new(value.ToString());
        public static implicit operator BigNumber(long value) => new(value.ToString());
        public static implicit operator BigNumber(ulong value) => new(value.ToString());
        public static implicit operator BigNumber(float value) => ConvertFromFloat(value);
        public static implicit operator BigNumber(double value) => ConvertFromFloat(value);
        public static implicit operator BigNumber(decimal value) => ConvertFromFloat(value);

        public static implicit operator string(BigNumber value) => value._value;
        public static implicit operator decimal(BigNumber value)
        {
            if (value > BigNumber.DecimalMax || value < BigNumber.DecimalMin) throw new ArgumentOutOfRangeException();
            return decimal.Parse(value._value);
        }
        public static implicit operator float(BigNumber value)
        {
            if (value > BigNumber.FloatMax || value < BigNumber.FloatMin) throw new ArgumentOutOfRangeException();
            return float.Parse(value._value);
        }
        public static implicit operator double(BigNumber value)
        {
            if (value > BigNumber.DoubleMax || value < BigNumber.DoubleMin) throw new ArgumentOutOfRangeException();
            return double.Parse(value._value);
        }
        public static implicit operator long(BigNumber value)
        {
            if (value > BigNumber.LongMax || value < BigNumber.LongMin) throw new ArgumentOutOfRangeException();
            return long.Parse(value._value);
        }
        public static implicit operator int(BigNumber value) => (int)(long)value;


        public static BigNumber operator +(BigNumber l, BigNumber r) => BigNumberAdd(l, r);
        public static BigNumber operator -(BigNumber l, BigNumber r) => BigNumberSubtract(l, r);
        public static BigNumber operator *(BigNumber l, BigNumber r) => BigNumberMultiply(l, r);
        public static BigNumber operator /(BigNumber l, BigNumber r) => BigNumberDivide(l, r);
        public static BigNumber operator %(BigNumber l, BigNumber r)
        {
            if (r._value == "0") throw new DivideByZeroException();
            if (l._value == "0") return new BigNumber();

            BigNumber num = new();
            num._value = l._value;
            string prefix = "";
            if (num._value[0] == '-')
            {
                prefix = "-";
                num._value = num._value.Substring(1);
            }

            BigNumber bn = num / r;

            int idx = bn._value.IndexOf('.');
            if (idx < 0) return new BigNumber();
            bn._value = bn._value.Substring(0, idx);
            num -= bn * r;

            num._value = prefix + num._value;
            return num;
        }


        public static bool operator ==(BigNumber l, BigNumber r)
        {
            if (object.ReferenceEquals(l, null) || object.ReferenceEquals(r, null)) return false;
            return l.Value == r.Value;
        }
        public static bool operator !=(BigNumber l, BigNumber r)
        {
            if (object.ReferenceEquals(l, null) || object.ReferenceEquals(r, null)) return false;
            return l.Value != r.Value;
        }
        public static bool operator <(BigNumber l, BigNumber r)
        {
            var ret = LessThen(l, r, out string result);
            if (ret.HasValue) return ret.Value;
            return result[0] == '-';
        }
        public static bool operator >(BigNumber l, BigNumber r)
        {
            var ret = GreaterThen(l, r, out string result);
            if (ret.HasValue) return ret.Value;
            return result[0] != '-' && result != "0";
        }
        public static bool operator <=(BigNumber l, BigNumber r)
        {
            var ret = LessThen(l, r, out string result);
            if (ret.HasValue) return ret.Value;
            return result[0] == '-' || result == "0";
        }
        public static bool operator >=(BigNumber l, BigNumber r)
        {
            var ret = GreaterThen(l, r, out string result);
            if (ret.HasValue) return ret.Value;
            return result[0] != '-';
        }

        public static bool operator ==(BigNumber l, float r) => EqualsFloat(l, r);
        public static bool operator !=(BigNumber l, float r) => !EqualsFloat(l, r);
        public static bool operator ==(BigNumber l, double r) => EqualsFloat(l, r);
        public static bool operator !=(BigNumber l, double r) => !EqualsFloat(l, r);
        public static bool operator ==(float l, BigNumber r) => EqualsFloat(r, l);
        public static bool operator !=(float l, BigNumber r) => !EqualsFloat(r, l);
        public static bool operator ==(double l, BigNumber r) => EqualsFloat(r, l);
        public static bool operator !=(double l, BigNumber r) => !EqualsFloat(r, l);

        #endregion

        #region 加减乘除

        // 加法 +
        static string BigNumberAdd(string num1, string num2)
        {
            if (num1 == "0") return num2;
            if (num2 == "0") return num1;
            int idx_dot = AlignDot(num1, num2, out string value1, out string value2);
            string result, prefix = "";
            if (value1[0] == '-')
            {
                value1 = value1.Substring(1);
                if (value2[0] == '-')
                {
                    value2 = value2.Substring(1);
                    prefix = "-";
                    result = Execute(BigIntegerAdd, value1, value2, idx_dot);
                }
                else result = Execute(BigIntegerSubtract, value2, value1, idx_dot);
            }
            else
            {
                if (value2[0] == '-')
                {
                    value2 = value2.Substring(1);
                    result = Execute(BigIntegerSubtract, value1, value2, idx_dot);
                }
                else result = Execute(BigIntegerAdd, value1, value2, idx_dot);
            }

            return PostResult(prefix, result, idx_dot);
        }

        // 减法 -
        static string BigNumberSubtract(string num1, string num2)
        {
            if (num2 == "0") return num1;
            if (num1 == "0")
            {
                if (num2 == "0") return "0";
                if (num2[0] == '-') return num2.Substring(1);
                return "-" + num2;
            }
            int idx_dot = AlignDot(num1, num2, out string value1, out string value2);
            string result, prefix = "";
            if (value1[0] == '-')
            {
                value1 = value1.Substring(1);
                if (value2[0] == '-')
                {
                    value2 = value2.Substring(1);
                    result = Execute(BigIntegerSubtract, value2, value1, idx_dot);
                }
                else
                {
                    prefix = "-";
                    result = Execute(BigIntegerAdd, value1, value2, idx_dot);
                }
            }
            else
            {
                if (value2[0] == '-')
                {
                    value2 = value2.Substring(1);
                    result = Execute(BigIntegerAdd, value1, value2, idx_dot);
                }
                else result = Execute(BigIntegerSubtract, value1, value2, idx_dot);
            }
            return PostResult(prefix, result, idx_dot);
        }

        // 乘法 *
        static string BigNumberMultiply(string num1, string num2)
        {
            string prefix = GetPrefix(num1, num2, out string value1, out string value2);
            // 小数点
            int idx, idx_dot = 0;
            idx = value1.IndexOf('.');
            if (idx > 0)
            {
                idx_dot += value1.Length - idx - 1;
                value1 = value1[0] == '0' ? value1.Substring(2) : value1.Remove(idx, 1);
                BigNumberPreProcess(value1, out value1);
            }
            idx = value2.IndexOf(".");
            if (idx > 0)
            {
                idx_dot += value2.Length - idx - 1;
                value2 = value2[0] == '0' ? value2.Substring(2) : value2.Remove(idx, 1);
                BigNumberPreProcess(value2, out value2);
            }
            // 计算
            List<string> listEachResult = new();
            List<char> listResult = new();
            for (int i = value2.Length - 1; i > -1; i--)
            {
                listResult.Clear();
                for (int j = i; j < value2.Length - 1; j++)
                    listResult.Add('0');
                int v = value2[i] - '0';
                if (v == 0) continue;
                int left = 0;
                for (int j = value1.Length - 1; j > -1; j--)
                {
                    int t = value1[j] - '0';
                    t *= v;
                    t += left;
                    left = t / 10;
                    t %= 10;
                    listResult.Add((char)('0' + t));
                }
                if (left > 0) listResult.Add((char)('0' + left));
                listResult.Reverse();
                listEachResult.Add(new string(listResult.ToArray()));
            }
            if (listEachResult.Count == 0) return "0";

            string result = listEachResult[0];
            listEachResult.RemoveAt(0);
            while (listEachResult.Count > 0)
            {
                result = BigIntegerAdd(result, listEachResult[0]);
                listEachResult.RemoveAt(0);
            }

            if (idx_dot > 0)
            {
                if (idx_dot >= result.Length) result = result.PadLeft(idx_dot + 1, '0');
                result = string.Concat(result.AsSpan(0, result.Length - idx_dot), ".", result.AsSpan(result.Length - idx_dot));
            }
            return prefix + result;
        }

        // 除法 ÷
        static string BigNumberDivide(string num1, string num2)
        {
            if (new BigNumber(num2) == Zero) throw new DivideByZeroException();
            string prefix = GetPrefix(num1, num2, out string value1, out string value2);
            // 小数点
            int idx, idx_dot = 0;
            idx = value1.IndexOf('.');
            if (idx > 0)
            {
                idx_dot += value1.Length - idx - 1;
                value1 = value1[0] == '0' ? value1.Substring(2) : value1.Remove(idx, 1);
                BigNumberPreProcess(value1, out value1);
            }
            idx = value2.IndexOf(".");
            if (idx > 0)
            {
                idx_dot -= value2.Length - idx - 1;
                value2 = value2[0] == '0' ? value2.Substring(2) : value2.Remove(idx, 1);
                BigNumberPreProcess(value2, out value2);
            }
            // 计算
            List<char> list = new();
            if (value1.Length < value2.Length)
            {
                idx_dot += value2.Length - value1.Length;
                value1 = value1.PadRight(value2.Length, '0');
            }

            BigNumber tmp = value1.Substring(0, value2.Length), x;
            value1 = value1.Substring(value2.Length);
            do
            {
                int v = 0;
                do
                {
                    x = tmp;
                    tmp -= value2;
                    v++;
                } while (tmp > Zero);
                if (tmp != Zero)
                {
                    v--;
                    tmp = x;
                }
                list.Add((char)('0' + v));
                if (value1.Length == 0) break;
                tmp = tmp._value + value1[0];
                value1 = value1.Substring(1);
            } while (true);

            int dot_cnt = 0;
            if (tmp != Zero)
            {
                list.Add('.');
                while (tmp != Zero)
                {
                    tmp._value += "0";
                    int v = 0;
                    do
                    {
                        x = tmp;
                        tmp -= value2;
                        v++;
                    } while (tmp > Zero);
                    if (tmp != Zero)
                    {
                        v--;
                        tmp = x;
                    }
                    list.Add((char)('0' + v));
                    dot_cnt++;

                    if (dot_cnt > PRECISION) break;
                }
            }

            if (idx_dot != 0)
            {
                idx = list.IndexOf('.');
                if (idx > 0)
                {
                    list.RemoveAt(idx);
                    if (idx > idx_dot)
                    {
                        idx -= idx_dot;
                        if (idx > list.Count)
                        {
                            while (idx > list.Count) list.Add('0');
                        }
                        else list.Insert(idx, '.');
                    }
                    else
                    {
                        idx_dot -= idx;
                        for (int i = 0; i < idx_dot; i++) list.Insert(0, '0');
                        list.Insert(0, '.');
                        list.Insert(0, '0');
                    }
                }
                else
                {
                    if (idx_dot < 0)
                    {
                        idx_dot = -idx_dot;
                        for (int i = 0; i < idx_dot; i++) list.Insert(0, '0');
                    }
                    else
                    {
                        while (list.Count <= idx_dot) list.Insert(0, '0');
                        list.Insert(list.Count - idx_dot, '.');
                    }
                }
            }
            return prefix + new string(list.ToArray());
        }

        #endregion

        // 大数字符串格式检查
        static bool CheckBigNumber(string num) => Regex.IsMatch(num, @"^[-]?(\d*[\.]?\d+)$");

        // 大数字符串预处理：掐头去尾
        static bool BigNumberPreProcess(string num, out string value)
        {
            if (!CheckBigNumber(num))
            {
                value = "0";
                return false;
            }
            string prefix = "";
            if (num[0] == '-')
            {
                prefix = "-";
                num = num.Substring(1);
            }
            int idx = 0;
            for (int i = 0; i < num.Length; i++)
            {
                if (num[i] != '0')
                {
                    idx = i;
                    break;
                }
            }
            int last = num.Length - 1;
            // 前导去 0
            if (num[idx] == '.') idx--;
            // 去除小数末尾的0
            if (num.Contains('.'))
            {
                for (int i = last; i > idx; i--)
                {
                    if (num[i] != '0')
                    {
                        last = i;
                        break;
                    }
                }
                if (num[last] == '.') last--;
            }
            value = num.Substring(idx, last - idx + 1);
            if (value == "0") return true;
            value = prefix + value;
            return true;
        }

        // 正整数 +
        static string BigIntegerAdd(string num1, string num2)
        {
            string value1, value2;
            BigNumberPreProcess(num1, out value1);
            BigNumberPreProcess(num2, out value2);
            if (value1.Length > value2.Length)
            {
                if (value2.Length == 1 && value2[0] == '0') return value1;
                value2 = value2.PadLeft(value1.Length, '0');
            }
            else
            {
                if (value1.Length == 1 && value1[0] == '0') return value2;
                value1 = value1.PadLeft(value2.Length, '0');
            }
            List<char> list = new();
            int left = 0;
            for (int i = value1.Length - 1; i > -1; i--)
            {
                int v = value1[i] - '0';
                v += value2[i] - '0';
                v += left;
                left = v / 10;
                v %= 10;
                list.Add((char)('0' + v));
            }
            if (left > 0) list.Add((char)('0' + left));
            list.Reverse();
            return new string(list.ToArray());
        }

        // 正整数 -
        static string BigIntegerSubtract(string num1, string num2)
        {
            string value1, value2;
            BigNumberPreProcess(num1, out num1);
            BigNumberPreProcess(num2, out num2);
            string prefix = "";
            if (num1.Length > num2.Length)
            {
                if (num2.Length == 1 && num2[0] == '0') return num1;
                value1 = num1;
                value2 = num2.PadLeft(num1.Length, '0');
            }
            else if (num1.Length < num2.Length)
            {
                if (num1.Length == 1 && num1[0] == '0') return "-" + num2;
                value1 = num2;
                value2 = num1.PadLeft(num2.Length, '0');
                prefix = "-";
            }
            else
            {
                for (int i = 0; i < num2.Length; i++)
                {
                    if (num1[i] != num2[i])
                    {
                        if (num1[i] < num2[i])
                        {
                            value1 = num2;
                            value2 = num1;
                            prefix = "-";
                        }
                        else
                        {
                            value1 = num1;
                            value2 = num2;
                        }
                        goto calc;
                    }
                }
                return "0";
            }
        calc:
            List<char> list = new();
            bool borrowed = false;
            for (int i = value1.Length - 1; i > -1; i--)
            {
                int v = value1[i];
                if (borrowed) v--;
                borrowed = false;
                if (v < value2[i])
                {
                    borrowed = true;
                    v += 10;
                }
                v -= value2[i];
                list.Add((char)('0' + v));
            }
            list.Reverse();
            return prefix + new string(list.ToArray());
        }

        // 加减法预处理：小数点对其
        static int AlignDot(string num1, string num2, out string value1, out string value2)
        {
            value1 = num1;
            value2 = num2;
            int idx1 = value1.IndexOf('.');
            int idx2 = value2.IndexOf(".");
            int sum1 = idx1 > 0 ? num1.Length - 1 - idx1 : idx1;
            int sum2 = idx2 > 0 ? num2.Length - 1 - idx2 : idx2;
            if (sum1 > sum2)
            {
                if (idx2 > 0) value2 = num2.PadRight(num2.Length + sum1 - sum2, '0');
                else value2 = num2 + "." + new string('0', sum1);
            }
            else if (sum2 > sum1)
            {
                if (idx1 > 0) value1 = num1.PadRight(num1.Length + sum2 - sum1, '0');
                else value1 = num1 + "." + new string('0', sum2);
                return sum2;
            }
            return sum1;
        }

        // 加减法中相同的处理过程
        static string Execute(Func<string, string, string> func, string num1, string num2, int idx)
        {
            if (idx < 0) return func(num1, num2);
            return func(num1.Remove(num1.Length - 1 - idx, 1), num2.Remove(num2.Length - 1 - idx, 1));
        }

        // float、double、decimal 转 BigNumber
        static BigNumber ConvertFromFloat<T>(T value) where T : struct
        {
            string? val = value.ToString();
            int idx = val.IndexOf('E');
            if (idx < 0)
            {
                if (val[val.Length - 1] == 'M') return new BigNumber(val.Substring(0, val.Length - 1));
                return new BigNumber(val);
            }

            BigNumber bn1 = val.Substring(0, idx);
            BigNumber bn2 = "1" + new string('0', int.Parse(val.Substring(idx + 2)));

            return val[idx + 1] == '+' ? bn1 * bn2 : bn1 / bn2;
        }

        static bool EqualsFloat<T>(BigNumber l, T r) where T : struct
        {
            string? f = r.ToString();
            int idx1 = l._value.IndexOf('.');
            int idx2 = f.IndexOf('.');
            if (idx1 != idx2) return false;
            int cnt = l._value.Length < f.Length ? l._value.Length : f.Length;
            cnt--;
            for (int i = 0; i < cnt; i++)
            {
                if (l._value[i] != f[i]) return false;
            }

            cnt = f[cnt] - l._value[cnt];
            return -3 < cnt && cnt < 3;
        }

        // 加减结果处理
        static string PostResult(string prefix, string result, int idx_dot)
        {
            if (result == "0") return "0";
            if (result[0] == '-')
            {
                prefix = "-";
                result = result.Substring(1);
            }
            if (result.Length <= idx_dot) result = result.PadLeft(idx_dot + 1, '0');
            if (idx_dot > 0) result = string.Concat(result.AsSpan(0, result.Length - idx_dot), ".", result.AsSpan(result.Length - idx_dot));
            return prefix + result;
        }

        // 乘除结果符号
        static string GetPrefix(string num1, string num2, out string value1, out string value2)
        {
            value1 = num1;
            value2 = num2;
            string prefix = "";
            if (value1[0] == '-')
            {
                prefix = "-";
                value1 = value1.Substring(1);
            }
            if (value2[0] == '-')
            {
                prefix = prefix.Length == 1 ? "" : "-";
                value2 = value2.Substring(1);
            }
            return prefix;
        }

        static bool? LessThen(BigNumber l, BigNumber r, out string result)
        {
            result = "";
            if (l._value[0] == '-')
            {
                if (r._value[0] != '-') return true;
            }
            else
            {
                if (r._value[0] == '-') return false;
            }
            result = BigNumberSubtract(l, r);

            return null;
        }

        static bool? GreaterThen(BigNumber l, BigNumber r, out string result)
        {
            result = "";
            if (l._value[0] == '-')
            {
                if (r._value[0] != '-') return false;
            }
            else
            {
                if (r._value[0] == '-') return true;
            }
            result = BigNumberSubtract(l, r);

            return null;
        }
    }
}
